
package com.jstarcraft.ai.jsat.distributions.kernels;

import com.jstarcraft.ai.jsat.DataSet;
import com.jstarcraft.ai.jsat.distributions.Distribution;
import com.jstarcraft.ai.jsat.distributions.discrete.UniformDiscrete;
import com.jstarcraft.ai.jsat.linear.Vec;

/**
 * Provides a Polynomial Kernel of the form <br>
 * k(x,y) = (alpha * x.y + c)^d
 * 
 * @author Edward Raff
 */
public class PolynomialKernel extends BaseKernelTrick {

    private static final long serialVersionUID = 9123109691782934745L;
    private double degree;
    private double alpha;
    private double c;

    /**
     * Creates a new polynomial kernel
     * 
     * @param degree the degree of the polynomial
     * @param alpha  the term to scale the dot product by
     * @param c      the additive term
     */
    public PolynomialKernel(double degree, double alpha, double c) {
        this.degree = degree;
        this.alpha = alpha;
        this.c = c;
    }

    /**
     * Defaults alpha = 1 and c = 1
     * 
     * @param degree the degree of the polynomial
     */
    public PolynomialKernel(double degree) {
        this(degree, 1, 1);
    }

    /**
     * Sets the scaling factor for the dot product, this is equivalent to
     * multiplying each value in the data set by a constant factor
     * 
     * @param alpha the scaling factor
     */
    public void setAlpha(double alpha) {
        if (Double.isInfinite(alpha) || Double.isNaN(alpha) || alpha == 0)
            throw new IllegalArgumentException("alpha must be a real non zero value, not " + alpha);
        this.alpha = alpha;
    }

    /**
     * Sets the additive term, when set to one this is equivalent to adding a bias
     * term of 1 to each vector. This is done after the scaling by
     * {@link #setAlpha(double) alpha}.
     * 
     * @param c the non negative additive term
     */
    public void setC(double c) {
        if (c < 0 || Double.isNaN(c) || Double.isInfinite(c))
            throw new IllegalArgumentException("C must be non negative, not " + c);
        this.c = c;
    }

    /**
     * Sets the degree of the polynomial
     * 
     * @param d the degree of the polynomial
     */
    public void setDegree(double d) {
        this.degree = d;
    }

    /**
     * Returns the scaling parameter
     * 
     * @return the scaling parameter
     */
    public double getAlpha() {
        return alpha;
    }

    /**
     * Returns the additive constant
     * 
     * @return the additive constant
     */
    public double getC() {
        return c;
    }

    /**
     * Returns the degree of the polynomial
     * 
     * @return the degree of the polynomial
     */
    public double getDegree() {
        return degree;
    }

    @Override
    public double eval(Vec a, Vec b) {
        return Math.pow(c + a.dot(b) * alpha, degree);
    }

    @Override
    public String toString() {
        return "Polynomial Kernel ( degree=" + degree + ", c=" + c + ", alpha=" + alpha + ")";
    }

    /**
     * Guesses the distribution to use for the degree parameter
     *
     * @param d the dataset to get the guess for
     * @return the guess for the degree parameter
     * @see #setDegree(double)
     */
    public static Distribution guessDegree(DataSet d) {
        return new UniformDiscrete(2, 9);
    }

    @Override
    public PolynomialKernel clone() {
        return new PolynomialKernel(degree, alpha, c);
    }
}
